package com.sensetime.gateway.filter;

import com.sensetime.jv.entity.LoginUser;
import com.sensetime.jv.enums.StatusEnum;
import com.sensetime.jv.exception.CustomException;
import com.sensetime.jv.feign.AuthFeignService;
import com.sensetime.jv.service.TokenCacheService;
import com.sensetime.jv.service.WhiteUrlCacheService;
import com.sensetime.jv.util.AssertUtil;
import com.sensetime.jv.util.JsonUtils;
import com.sensetime.jv.util.JwtUtil;
import com.sensetime.jv.vo.request.UrlRolePermissionRespVO;
import com.sensetime.jv.vo.request.WhiteUrlRequestVO;
import jakarta.annotation.Resource;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.ThreadContext;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;

import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import static com.sensetime.jv.constants.CustomConstant.*;

@Component
@Slf4j
@Order(1) // 指定执行顺序
public class CommonFilterGatewayFilterFactory
        extends AbstractGatewayFilterFactory<CommonFilterGatewayFilterFactory.Config> {


    @Resource
    @Lazy
    private AuthFeignService feignService;
    @Resource
    private TokenCacheService tokenCacheService;
    @Resource
    private WhiteUrlCacheService whiteUrlCacheService;

    private final AntPathMatcher matcher = new AntPathMatcher();

    public CommonFilterGatewayFilterFactory() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            // 1. 获取白名单URL列表
            List<WhiteUrlRequestVO> whiteUrls = whiteUrlCacheService.getWhiteUrls();
            String requestId =  ThreadContext.get(REQUEST_ID);
            exchange.getResponse().getHeaders().add(REQUEST_ID_HEADER, requestId); // 前端也能拿到

            // 2. 获取当前请求的URL和HTTP方法
            ServerHttpRequest request = exchange.getRequest();
            String requestUrl = request.getURI().getPath();
            HttpMethod requestMethod = request.getMethod(); // 获取HttpMethod枚举
            String methodValue = requestMethod.name(); // 转换为字符串
            log.info("<<<<<<<<<<<<<<<<<<<<<<<<<[访问的路径:{}] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<", requestUrl);
            // 3. 检查当前请求是否在白名单中
            boolean isWhiteUrl = isUrlInWhiteList(whiteUrls, requestUrl, methodValue);

            log.info("<<<<<<<<<<<<<<<<<<<<<<<<<[{}] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<", config.getMsg());

            // 如果是白名单URL，直接放行，无需校验token
            if (isWhiteUrl) {
                log.info("请求 {} 属于白名单，无需校验token", requestUrl);
                ServerHttpRequest mutatedRequest = request.mutate()
                        .header(REQUEST_ID_HEADER, requestId)
                        .build();
                return chain.filter(exchange.mutate().request(mutatedRequest).build());
            }


            // 1. 尝试从 Redis 获取用户信息
            LoginUser userCache = getUserFromRedis(exchange);
            AssertUtil.notNull(userCache,"登陆状态已过期，请重新登录",StatusEnum.FORBIDDEN);
            AssertUtil.isTrue(hasPermission(userCache, exchange),"您没有访问权限",StatusEnum.FORBIDDEN);
            // 3. 将LoginUser对象序列化为JSON字符串并设置到请求头中
            String loginUserJson = JsonUtils.writeValueAsString(userCache);
            ServerHttpRequest mutatedRequest = request.mutate()
                    .header(USERINFO_HEADER, loginUserJson)
                    .header(REQUEST_ID_HEADER, requestId)
                    .build();
            return chain.filter(exchange.mutate().request(mutatedRequest).build());
        };
    }


    /**
     * 使用PathMatcher检查请求的URL是否在白名单中
     */
    private boolean isUrlInWhiteList(List<WhiteUrlRequestVO> whiteUrls, String requestUrl, String requestMethod) {
        if (CollectionUtils.isEmpty(whiteUrls)) {
            return false;
        }



        return whiteUrls.stream()
                .anyMatch(whiteUrl -> {
                    // 拼接servletPath和url，并处理多余的斜杠
                    String fullPath = normalizePath(whiteUrl.getServletPath(), whiteUrl.getUrl());

                    // 使用规范化后的路径进行匹配
                    return matcher.match(fullPath, requestUrl) &&
                            requestMethod.equalsIgnoreCase(whiteUrl.getMethod());
                });
    }

    /**
     * 规范化路径：拼接basePath和path，并确保它们之间只有一个斜杠
     */
    private String normalizePath(String basePath, String path) {
        if (basePath == null) basePath = "";
        if (path == null) path = "";

        // 移除basePath末尾的斜杠和path开头的斜杠
        String cleanedBasePath = basePath.replaceAll("/+$", "");
        String cleanedPath = path.replaceAll("^/+", "");

        // 拼接并确保中间只有一个斜杠
        return cleanedBasePath.isEmpty() ? cleanedPath : cleanedBasePath + "/" + cleanedPath;
    }


    private LoginUser getUserFromRedis(ServerWebExchange exchange) {
        String token = exchange.getRequest().getHeaders().getFirst(TOKEN_HEADER);
        if (!StringUtils.hasText(token)) {
            return null;
        }
        try {
            String userId = JwtUtil.parseString(token);
            if (!tokenCacheService.validateToken(userId, token)) {
                return null;
            }
            LoginUser user = tokenCacheService.getLoginUser(userId);
            if (user == null) {
                return null;
            }
            tokenCacheService.refreshToken(userId); // 续期
            return user;
        } catch (CustomException ex) {
            throw ex;               // 业务异常继续往外抛
        } catch (Exception ex) {
            log.error("<<<<<<<<<ex<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>",ex);
            return null;
        }
    }

    private boolean hasPermission(LoginUser userCache, ServerWebExchange exchange) {
        List<UrlRolePermissionRespVO> all = feignService.getAllPermissions();
        List<UrlRolePermissionRespVO> matched = match(
                exchange.getRequest().getPath().toString(),
                exchange.getRequest().getMethod().toString(),
                all
        );

        // ① 没配置权限 → 默认放行
        if (matched.isEmpty()) {
            return true;
        }

        // ② 提取所有匹配的 permissionId 和 roleId
        Set<Integer> requiredPermissionIds = matched.stream()
                .map(UrlRolePermissionRespVO::getPermissionId)
                .collect(Collectors.toSet());

        // 过滤掉 null 值的 roleId
        Set<Integer> requiredRoleIds = matched.stream()
                .map(UrlRolePermissionRespVO::getRoleId)
                .filter(Objects::nonNull)  // 过滤 null 值
                .collect(Collectors.toSet());

        // ③ 判断用户是否拥有至少一个匹配的权限
        boolean hasPermission = userCache.getPermissions().stream()
                .anyMatch(p -> requiredPermissionIds.contains(p.getId()));

        if (!hasPermission) {
            return false;
        }

        // ④ 判断用户是否拥有至少一个匹配的角色
        if (requiredRoleIds.isEmpty()){
            return true;
        }
        return userCache.getRoles().stream()
                .anyMatch(r -> requiredRoleIds.contains(r.getId()));
    }

    private List<UrlRolePermissionRespVO> match(String path, String method, List<UrlRolePermissionRespVO> all) {
        if (all == null || all.isEmpty()) {
            return Collections.emptyList();
        }

        return all.stream()
                .filter(p -> matcher.match(
                        normalizePath(p.getServletPath(), p.getUrl()), path) &&
                        method.equalsIgnoreCase(p.getMethod()))
                .collect(Collectors.toList());
    }

    @Data
    public static class Config {
        private String msg;
    }
}